/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.web.task.zkex.simple;

import cn.easyplatform.EasyPlatformWithLabelKeyException;
import cn.easyplatform.lang.Strings;
import cn.easyplatform.messages.request.GetListRequestMessage;
import cn.easyplatform.messages.request.addon.SaveListRequestMessage;
import cn.easyplatform.messages.response.GetListResponseMessage;
import cn.easyplatform.messages.response.SimpleResponseMessage;
import cn.easyplatform.messages.vos.GetListVo;
import cn.easyplatform.messages.vos.component.ListPageVo;
import cn.easyplatform.messages.vos.datalist.ListGetListVo;
import cn.easyplatform.messages.vos.datalist.ListHeaderVo;
import cn.easyplatform.messages.vos.datalist.ListVo;
import cn.easyplatform.spi.service.ComponentService;
import cn.easyplatform.type.CrossRowVo;
import cn.easyplatform.type.FieldVo;
import cn.easyplatform.type.IResponseMessage;
import cn.easyplatform.type.ListRowVo;
import cn.easyplatform.web.exporter.excel.ExcelExporter;
import cn.easyplatform.web.exporter.pdf.PdfExporter;
import cn.easyplatform.web.ext.ComponentBuilder;
import cn.easyplatform.web.ext.Exportable;
import cn.easyplatform.web.ext.Runnable;
import cn.easyplatform.web.ext.Widget;
import cn.easyplatform.web.ext.zul.ListboxExt;
import cn.easyplatform.web.service.ServiceLocator;
import cn.easyplatform.web.task.zkex.ListSupport;
import cn.easyplatform.web.task.OperableHandler;
import cn.easyplatform.web.task.BackendException;
import cn.easyplatform.web.task.event.EventListenerHandler;
import cn.easyplatform.web.task.support.ExpressionEngine;
import cn.easyplatform.web.task.support.SupportFactory;
import cn.easyplatform.web.utils.ExtUtils;
import cn.easyplatform.web.utils.PageUtils;
import cn.easyplatform.web.utils.WebUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.util.media.AMedia;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Components;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.*;
import org.zkoss.zk.ui.metainfo.EventHandler;
import org.zkoss.zk.ui.metainfo.EventHandlerMap;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zkmax.zul.Filedownload;
import org.zkoss.zul.*;
import org.zkoss.zul.event.PagingEvent;
import org.zkoss.zul.event.ZulEvents;

import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.util.*;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public class ListboxBuilder implements ComponentBuilder, Widget, Exportable, Runnable, EventListener<Event> {

    private ListboxExt list;

    private OperableHandler main;

    private GetListVo gv;

    private Paging paging;

    private ComponentService cs;

    private TotalHelper[] totalHelper;

    private String[] columns;

    private String eventName = "";

    private EventListenerHandler elh;

    private ListSupport support;

    private Component anchor;

    public ListboxBuilder(OperableHandler mainTaskHandler, ListboxExt cbx) {
        this.main = mainTaskHandler;
        this.list = cbx;
    }

    public ListboxBuilder(ListSupport support, ListboxExt cbx, Component anchor) {
        this.support = support;
        this.main = support.getMainHandler();
        this.list = cbx;
        this.anchor = anchor;
    }

    @Override
    public Component build() {
//        if (Strings.isBlank(list.getQuery()))
//            throw new EasyPlatformWithLabelKeyException("component.property.not.found", "<listbox>", "query");
        if (list.isCross()) {
            if (Strings.isBlank(list.getQuery()))
                throw new EasyPlatformWithLabelKeyException("component.property.not.found", "<listbox>", "query");
            if (Strings.isBlank(list.getRowQuery()))
                throw new EasyPlatformWithLabelKeyException("component.property.not.found", "<listbox>", "rowQuery");
            if (Strings.isBlank(list.getGroupQuery()))
                throw new EasyPlatformWithLabelKeyException("component.property.not.found", "<listbox>", "groupQuery");
        } else if (!Strings.isBlank(list.getGroupQuery())) {
            if (Strings.isBlank(list.getGroupField()))
                throw new EasyPlatformWithLabelKeyException("component.property.not.found", "<listbox>", "groupField");
        }
        if (!Strings.isBlank(list.getTotalColumns()) && !Strings.isBlank(list.getTotalScript())) {
            String[] cols = list.getTotalColumns().split(",");
            totalHelper = new TotalHelper[cols.length];
            for (int i = 0; i < cols.length; i++)
                totalHelper[i] = new TotalHelper(cols[i].toUpperCase());
        }
        list.setAttribute("$proxy", this);
        cs = ServiceLocator.lookup(ComponentService.class);
        //表示原生的，不需要设置
        if (list.getListhead() != null)
            list.getListhead().setAttribute("o", 1);
        createHeads();
        if (list.getPageSize() > 0) {
            if (!list.isFetchAll()) {
                Vlayout layout = new Vlayout();
                layout.setSpacing("0");
                layout.setHflex(list.getHflex());
                layout.setVflex(list.getVflex());
                layout.setVisible(list.isVisible());
                layout.setStyle(list.getStyle());
                layout.setTop(list.getTop());
                layout.setLeft(list.getLeft());
                layout.setHeight(list.getHeight());
                layout.setWidth(list.getWidth());
                list.setHeight(null);
                list.setWidth(null);
                list.setHflex("1");
                list.setVflex("1");
                list.setStyle(null);
                list.setTop(null);
                list.setLeft(null);
                Components.replace(list, layout);
                list.setParent(layout);
                paging = new Paging();
                paging.setHflex("1");
                paging.setHeight("30px");
                paging.setDetailed(list.isPagingDetailed());
                paging.setMold(list.getPagingMold());
                paging.setPageSize(list.getPageSize());
                if (list.getPagingStyle() != null)
                    paging.setStyle(list.getPagingStyle());
                paging.addEventListener(ZulEvents.ON_PAGING, this);
                paging.setParent(layout);
            } else {
                list.setMold("paging");
                list.getPaginal().setPageSize(list.getPageSize());
                paging = list.getPagingChild();
            }
            list.setPaging(paging);
        }
        // 设置事件
        if (!Strings.isBlank(list.getEvent())) {
            String event = list.getEvent();
            if (Events.isValid(event)) {
                char[] cs = event.toCharArray();
                int i = 0;
                boolean isSep = false;
                StringBuilder sb = new StringBuilder();
                while (true) {
                    if (cs[i] == ':') {
                        isSep = true;
                        break;
                    }
                    if (Character.isLetter(cs[i]))
                        sb.append(cs[i]);
                    else
                        break;
                    i++;
                }
                if (isSep && sb.length() > 0) {
                    eventName = sb.toString();
                    event = event.substring(i + 1).trim();
                }
                sb = null;
            } else
                eventName = Events.ON_CLICK;
            if (Strings.isBlank(list.getQuery())) {
                PageUtils.addEventListener(main, eventName, list);
            } else
                elh = new EventListenerHandler(eventName, main, event, anchor);
        } else {
            EventHandlerMap em = list.getEventHandlerMap();
            if (em != null && !em.isEmpty()) {
                em.getEventNames().forEach(name -> {
                    List<EventHandler> evts = em.getAll(name);
                    evts.forEach(eh -> {
                        if (eh.isEffective(list)) {
                            if (Strings.isBlank(list.getQuery())) {
                                elh = new EventListenerHandler(name,
                                        main, eh.getZScript().getRawContent(), anchor);
                            } else {
                                list.addEventListener(name, new EventListenerHandler(name,
                                        main, eh.getZScript().getRawContent(), anchor));
                            }
                        }
                    });
                });
            }
        }
        if (list.isImmediate())
            createItems();
        if (!Strings.isBlank(list.getGroupField()) && list.isMultiple() && list.isCheckmark()) {
            list.setAttribute("org.zkoss.zul.listbox.groupSelect", true);
            list.setAttribute("org.zkoss.zul.listbox.rod", false);
            list.addEventListener(Events.ON_SELECT, new EventListener<SelectEvent<Listbox, Listitem>>() {
                @Override
                public void onEvent(SelectEvent<Listbox, Listitem> evt) throws Exception {
                    Component c = evt.getReference();
                    if (c instanceof Listgroup) {
                        Listgroup lg = (Listgroup) c;
                        for (Listitem li : lg.getItems())
                            li.setSelected(lg.isSelected());
                    }
                }
            });
        }
        return list;
    }

    private void createHeads() {
        if (list.isCross()) {
            if (list.getFirstChild() != null)
                list.getChildren().clear();
            ComponentService cs = ServiceLocator
                    .lookup(ComponentService.class);
            //行分组表头
            GetListVo gv = null;
            if (anchor != null) {
                ListRowVo rowVo = WebUtils.getAnchorValue(anchor);
                Object[] data = support.isCustom() ? rowVo.getData() : rowVo
                        .getKeys();
                gv = new ListGetListVo(list.getDbId(), list.getGroupQuery(), support
                        .getComponent().getId(), data);
            } else {
                gv = new GetListVo(list.getDbId(), list.getGroupQuery());
            }
            GetListRequestMessage req = new GetListRequestMessage(main.getId(), gv);
            IResponseMessage<?> resp = cs.getList(req);
            if (resp.isSuccess()) {
                List<FieldVo[]> headers = (List<FieldVo[]>) ((GetListResponseMessage) resp)
                        .getBody();
                Auxhead auxhead = new Auxhead();
                auxhead.setParent(list);
                Auxheader auxheader = new Auxheader();
                auxheader.setColspan(1);
                auxheader.setParent(auxhead);
                StringBuilder sb = new StringBuilder(20);
                for (FieldVo[] fds : headers) {
                    for (FieldVo fv : fds)
                        sb.append(fv.getValue()).append(".");
                    auxheader = new Auxheader(sb.deleteCharAt(sb.length() - 1).toString());
                    auxheader.setAlign("center");
                    auxheader.setParent(auxhead);
                    auxheader.setAttribute("group", fds);
                    sb.setLength(0);
                }
            } else
                throw new BackendException(resp);
            req = new GetListRequestMessage(main.getId(),
                    new GetListVo(list.getDbId(), list.getRowQuery()));
            resp = cs.getList(req);
            if (resp.isSuccess()) {
                Listhead listhead = new Listhead();
                listhead.setParent(list);
                List<FieldVo[]> headers = (List<FieldVo[]>) ((GetListResponseMessage) resp)
                        .getBody();
                if (!headers.isEmpty()) {
                    Listheader listheader = new Listheader(headers.get(0)[0].getName());
                    listheader.setAlign("center");
                    listhead.appendChild(listheader);
                }
                for (FieldVo[] fds : headers) {
                    Listitem li = new Listitem();
                    Listcell lc = new Listcell(fds[0].getValue().toString());
                    lc.setValue(fds[0].getValue());
                    lc.setZclass("z-listheader");
                    li.appendChild(lc);
                    li.setParent(list);
                }
            } else
                list.setEmptyMessage((String) resp.getBody());
        } else {
            list.setCols(0);
            if (list.getListhead() != null) {
                if (list.getListhead().getAttribute("o") == null) {
                    if (!Strings.isBlank(list.getTitle()) && Strings.isBlank(list.getQuery())) {
                    } else {
                        list.getListhead().detach();
                        createNormalHeader();
                    }
                }
            } else {
                createNormalHeader();
            }
        }
    }

    /**
     * 创建正常表头
     */
    private void createNormalHeader() {
        if (!Strings.isBlank(list.getTitle())) {
            String title = list.getTitle();
            if (title.startsWith("$")) {
                Object val = WebUtils.getFieldValue(main, title.substring(1));
                if (val == null || val.toString().equals(""))
                    return;
                title = (String) val;
            }
            Listhead head = new Listhead();
            head.setSizable(list.isSizable());
            String[] headers = title.split(",");
            for (int i = 0; i < list.getUnionRows(); i++) {
                for (String h : headers) {
                    Listheader header = new Listheader(h.trim());
                    if (!Strings.isBlank(list.getHeadStyle()))
                        header.setStyle(list.getHeadStyle());
                    if (list.isSizedByContent())
                        header.setHflex("1");
                    else
                        header.setWidth(header.getLabel().length() * 15 + "px");
                    head.appendChild(header);
                }
            }
            list.appendChild(head);
            if (list.getFrozen() == null && list.getFrozenColumns() > 0) {
                Frozen frozen = new Frozen();
                if (!Strings.isBlank(list.getFrozenStyle()))
                    frozen.setStyle(list.getFrozenStyle());
                frozen.setColumns(list.getFrozenColumns());
                list.appendChild(frozen);
            }
        }
        if (list.getListfoot() != null)
            list.getListfoot().detach();
    }

    /**
     * 创建交叉表记录
     *
     * @param data
     */
    private void createCrosstableItems(List<String> groupName, List<FieldVo[]> data) {
        List<Auxheader> auxheaders = list.getFirstChild().getChildren();
        for (int i = 1; i < auxheaders.size(); i++) {
            Auxheader auxheader = auxheaders.get(i);
            FieldVo[] group = (FieldVo[]) auxheader.getAttribute("group");
            List<FieldVo[]> rows = getRows(group, data);
            for (Listitem li : list.getItems()) {
                Listcell lc = (Listcell) li.getFirstChild();
                Object value = lc.getValue();
                FieldVo[] fvs = getRow(groupName.get(0), value, rows);
                int idx = ((i - 1) * auxheader.getColspan()) + 1;
                lc = (Listcell) li.getChildren().get(idx);
                if (fvs != null) {//填充记录
                    for (FieldVo fv : fvs) {
                        if (!groupName.contains(fv.getName())) {
                            Component cell = li.getChildren().get(idx);
                            Component c = cell.getFirstChild();
                            if (c == null)
                                PageUtils.setValue(cell, fv.getValue());
                            else
                                PageUtils.setValue(c, fv.getValue());
                            idx++;
                        }//if
                    }//for
                    //保存当前记录到第1格子
                    CrossRowVo crv = new CrossRowVo(fvs);
                    lc.setValue(crv);
                } else {//创建新记录
                    fvs = data.get(0);
                    FieldVo[] clone = new FieldVo[fvs.length];
                    for (int j = 0; j < fvs.length; j++) {
                        clone[j] = fvs[j].clone();
                        if (clone[j].getName().equals(groupName.get(0))) {
                            clone[j].setValue(value);
                        } else {
                            for (FieldVo fv : group) {
                                if (clone[j].getName().equals(fv.getName())) {
                                    clone[j].setValue(fv.getValue());
                                    break;
                                }
                            }
                        }
                    }
                    CrossRowVo crv = new CrossRowVo(clone);
                    lc.setValue(crv);
                }//else
            }//for
        }//for
    }

    /**
     * 取单笔数据
     *
     * @param name
     * @param value
     * @param data
     * @return
     */
    private FieldVo[] getRow(String name, Object value, List<FieldVo[]> data) {
        for (FieldVo[] fvs : data) {
            for (FieldVo fv : fvs) {
                if (name.equals(fv.getName()) && Objects.equals(value, fv.getValue()))
                    return fvs;
            }
        }
        return null;
    }

    /**
     * 取组数据
     *
     * @param group
     * @param data
     * @return
     */
    private List<FieldVo[]> getRows(FieldVo[] group, List<FieldVo[]> data) {
        List<FieldVo[]> cols = new ArrayList<>();
        Iterator<FieldVo[]> itr = data.iterator();
        while (itr.hasNext()) {
            FieldVo[] fvs = itr.next();
            boolean find = false;
            for (FieldVo src : group) {
                for (FieldVo target : fvs) {
                    find = target.getName().equals(src.getName()) && Objects.equals(src.getValue(), target.getValue());
                    if (find)
                        break;
                }
                if (!find)
                    break;
            }
            if (find) {
                cols.add(fvs);
                //至少留一笔给空的记录赋值
                if (itr.hasNext())
                    itr.remove();
            }
        }
        return cols;
    }

    /**
     * 生成表格记录项
     */
    private void createItems() {
        if (!Strings.isBlank(list.getQuery())) {
            if (anchor != null) {
                ListRowVo rowVo = WebUtils.getAnchorValue(anchor);
                Object[] data = support.isCustom() ? rowVo.getData() : rowVo
                        .getKeys();
                gv = new ListGetListVo(list.getDbId(), list.getQuery(), support
                        .getComponent().getId(), data);
            } else {
                gv = new GetListVo(list.getDbId(), list.getQuery());
            }
            gv.setFilter(list.getFilter());
            gv.setPageSize(list.isFetchAll() ? 0 : list.getPageSize() * list.getUnionRows());
            gv.setOrderBy(list.getOrderBy());
            gv.setGetCount(!list.isCross());
            gv.setGetMetadataIfEmpty(list.isCross());
            GetListRequestMessage req = new GetListRequestMessage(main.getId(), gv);
            IResponseMessage<?> resp = cs.getList(req);
            if (!resp.isSuccess()) {
                Clients.wrongValue(list, (String) resp.getBody());
                return;
            }
            if (list.isCross()) {
                FieldVo[] fvs = null;
                List<FieldVo[]> data = null;
                if (resp instanceof SimpleResponseMessage) {//空的记录
                    fvs = (FieldVo[]) resp.getBody();
                    data = new ArrayList<>(1);
                    data.add(fvs);
                } else {
                    data = (List<FieldVo[]>) ((GetListResponseMessage) resp).getBody();
                    fvs = data.get(0);
                }
                Auxhead auxhead = (Auxhead) list.getFirstChild();
                List<Auxheader> auxheaders = auxhead.getChildren();
                List<String> groupName = new ArrayList<>();
                Listhead listhead = list.getListhead();
                groupName.add(((Listheader) listhead.getChildren().get(0)).getLabel());
                Auxheader auxheader = auxheaders.get(1);
                FieldVo[] group = (FieldVo[]) auxheader.getAttribute("group");
                for (FieldVo f : group)
                    groupName.add(f.getName());
                for (int i = 1; i < auxheaders.size(); i++) {
                    auxheader = auxheaders.get(i);
                    auxheader.setColspan(fvs.length - groupName.size());
                    for (FieldVo fv : fvs) {
                        if (!groupName.contains(fv.getName())) {
                            Listheader listheader = new Listheader(fv.getName());
                            listheader.setParent(listhead);
                        }
                    }
                }
                List<String> editableColumns = null;
                if (!Strings.isBlank(list.getEditableColumns()))
                    editableColumns = Arrays.asList(list.getEditableColumns().split(","));
                CrosstableEventListener evl = new CrosstableEventListener();
                //先填充，不管有没有数据
                for (Listitem li : list.getItems()) {
                    for (int i = 1; i < auxheaders.size(); i++) {
                        for (FieldVo fv : fvs) {
                            if (!groupName.contains(fv.getName())) {
                                Listcell lc = new Listcell();
                                if (editableColumns != null) {
                                    if (editableColumns.contains(fv.getName()) || editableColumns.get(0).equals("*")) {
                                        Component c = PageUtils.createInputComponent(fv.getType());
                                        c.setAttribute("field", fv.getName());
                                        c.addEventListener(PageUtils.getComponentDefaultEventName(c), evl);
                                        lc.appendChild(c);
                                    }
                                }//if
                                lc.setParent(li);
                            }
                        }//for
                    }//for
                }//for
                createCrosstableItems(groupName, data);
            } else {
                list.getItems().clear();
                List<FieldVo[]> data = null;
                ListPageVo pv = null;
                if (resp.getBody() instanceof ListPageVo) {
                    pv = (ListPageVo) resp.getBody();
                    data = pv.getData();
                } else
                    data = (List<FieldVo[]>) ((GetListResponseMessage) resp).getBody();
                if (!data.isEmpty()) {
                    redraw(data);
                    if (pv != null) {
                        if (pv.getTotalCount() > gv.getPageSize()) {
                            paging.setTotalSize(pv.getTotalCount());
                            paging.setPageSize(gv.getPageSize());
                            paging.setVisible(true);
                        } else
                            paging.setVisible(false);
                    }
                } else if (paging != null)
                    paging.setVisible(false);
            }
        }
    }

    /**
     * 绘制正常表记录
     *
     * @param data
     */
    protected void redraw(List<FieldVo[]> data) {
        Map<Object, Node> map = null;
        if (!Strings.isBlank(list.getGroupField()) && Strings.isBlank(list.getGroupQuery()))
            map = new LinkedHashMap<Object, Node>();
        ExpressionEngine expressionEngine = null;
        Map<String, Object> evalMap = null;
        Map<String, Component> managedComponents = null;
        if (!Strings.isBlank(list.getRowScript())) {
            expressionEngine = SupportFactory.getExpressionEngine(main, list.getRowScript());
            expressionEngine.compile();
            evalMap = new HashMap<String, Object>();
            managedComponents = new HashMap<String, Component>();
        }
        if (totalHelper != null) {
            for (TotalHelper h : totalHelper) {
                h.total = null;
                h.total = new BigDecimal(0);
            }
        }
        try {
            boolean createTotal = true;
            int size = data.size();
            for (int i = 0; i < size; i++) {
                FieldVo[] fvs = data.get(i);
                if (map != null) {
                    for (FieldVo fv : fvs) {
                        if (fv.getName().equals(list.getGroupField())) {
                            Node node = map.get(fv.getValue());
                            if (node == null) {
                                node = new Node();
                                node.key = fv.getValue();
                                if (!Strings.isBlank(list.getGroupNameField())) {
                                    for (FieldVo f : fvs) {
                                        if (f.getName().equals(list.getGroupNameField())) {
                                            node.name = f.getValue() == null ? node.key.toString()
                                                    : f.getValue().toString();
                                            break;
                                        }
                                    }
                                } else
                                    node.name = node.key.toString();
                                map.put(node.key, node);
                            }
                            node.children.add(fvs);
                            break;
                        }
                    } // for
                } else if (!Strings.isBlank(list.getGroupQuery())) {// groupQuery
                    Listgroup g = new Listgroup();
                    if (!Strings.isBlank(list.getGroupNameField())) {
                        for (FieldVo f : fvs) {
                            if (f.getName().equals(list.getGroupNameField())) {
                                g.setLabel((String) f.getValue());
                                break;
                            }
                        }
                    } else {
                        for (FieldVo f : fvs) {
                            if (f.getName().equals(list.getGroupField())) {
                                g.setLabel((String) f.getValue());
                                break;
                            }
                        }
                    }
                    g.setValue(fvs);
                    if (list.isLazy()) {
                        g.addEventListener(Events.ON_OPEN, this);
                        g.setOpen(false);
                        createTotal = false;
                    }
                    list.appendChild(g);
                    if (!list.isLazy())
                        loadGroup(expressionEngine, evalMap, managedComponents, g);
                } else {
                    if (evalMap != null) {
                        evalMap.clear();
                        managedComponents.clear();
                    }
                    if (list.getUnionRows() > 1) {
                        FieldVo[][] rowData = new FieldVo[list.getUnionRows()][fvs.length];
                        for (int j = 0; j < rowData.length; j++) {
                            if ((i + j) < size)
                                rowData[j] = data.get(i + j);
                        }
                        createUnionRow(list.getItems().size(), rowData, evalMap, managedComponents, expressionEngine);
                        i += list.getUnionRows() - 1;
                    } else {
                        Listitem anchor = createRow(list.getItems().size(), fvs, evalMap, managedComponents, null);
                        if (expressionEngine != null)
                            expressionEngine.eval(anchor, evalMap, managedComponents);
                    }
                }
            }
            if (map != null) {
                for (Node node : map.values()) {
                    Listgroup g = new Listgroup();
                    g.setLabel(node.name);
                    TotalHelper[] groupTotals = null;
                    if (totalHelper != null) {
                        groupTotals = new TotalHelper[totalHelper.length];
                        for (int i = 0; i < totalHelper.length; i++)
                            groupTotals[i] = new TotalHelper(totalHelper[i].name);
                    }
                    list.appendChild(g);
                    for (FieldVo[] fvs : node.children) {
                        if (evalMap != null) {
                            evalMap.clear();
                            managedComponents.clear();
                        }
                        Listitem anchor = createRow(list.getItems().size(), fvs, evalMap, managedComponents, groupTotals);
                        if (expressionEngine != null)
                            expressionEngine.eval(anchor, evalMap, managedComponents);
                    }
                    if (groupTotals != null)
                        createFoot(g, groupTotals);
                }
            }
            if (createTotal && totalHelper != null)
                createFoot(list, totalHelper);
        } finally {
            if (expressionEngine != null) {
                evalMap = null;
                managedComponents = null;
                expressionEngine.destroy();
                expressionEngine = null;
            }
        }
    }

    private void createFoot(Component parent, TotalHelper[] data) {
        ExpressionEngine footEngine = SupportFactory.getExpressionEngine(main, list.getTotalScript());
        Map<String, Component> managedComponents = new HashMap<String, Component>();
        // 创建新的统计行
        if (parent instanceof Listbox) {
            if (((Listbox) parent).getListfoot() == null) {
                Listfoot foot = new Listfoot();
                for (int i = 0; i < columns.length; i++) {
                    Listfooter footer = new Listfooter();
                    footer.setParent(foot);
                    managedComponents.put(columns[i], footer);
                }
                foot.setParent(parent);
            } else {
                List<Component> footers = (((Listbox) parent).getListfoot()).getChildren();
                for (int i = 0; i < columns.length; i++)
                    managedComponents.put(columns[i], footers.get(i));
            }
        } else {
            managedComponents.put(columns[0], parent.getFirstChild());
            for (int i = 1; i < columns.length; i++) {
                Listcell footer = new Listcell();
                footer.setParent(parent);
                managedComponents.put(columns[i], footer);
            }
        }
        Map<String, Object> evalMap = new HashMap<String, Object>(data.length);
        for (TotalHelper h : data)
            evalMap.put(h.name, h.total.doubleValue());
        if (parent instanceof Listbox) {
            evalMap.put("_TYPE_", "FOOT");
            evalMap.put("_COUNT_", list.getItemCount() - list.getGroupCount());
        } else {
            evalMap.put("_TYPE_", "GROUP");
            evalMap.put("_COUNT_", ((Listgroup) parent).getItemCount());
        }
        footEngine.exec(null, managedComponents, evalMap);
        footEngine = null;
        managedComponents = null;
        evalMap = null;
    }

    /**
     * 生成多笔记录的表行
     *
     * @param index
     * @param data
     * @param evalMap
     * @param managedComponents
     * @param expressionEngine
     */
    private void createUnionRow(int index, FieldVo[][] data, Map<String, Object> evalMap,
                                Map<String, Component> managedComponents, ExpressionEngine expressionEngine) {
        if (Strings.isBlank(list.getValueField()))
            list.setValueField(data[0][0].getName());
        Listitem item = new Listitem();
        if (!Strings.isBlank(list.getRowStyle()))
            item.setStyle(list.getRowStyle());
        item.setValue(data[0]);
        if (columns == null)
            columns = new String[data[0].length * list.getUnionRows()];
        int pos = 0;
        for (int i = 0; i < data.length; i++) {
            FieldVo[] fvs = data[i];
            if (fvs[0] == null)
                item.appendChild(new Listcell());
            else {
                if (evalMap != null) {
                    for (FieldVo fv : fvs)
                        evalMap.put(fv.getName(), fv.getValue());
                }
                for (int j = 0; j < fvs.length; j++, pos++) {
                    FieldVo field = fvs[j];
                    columns[pos] = field.getName();
                    Listcell cell = new Listcell();
                    if (field.getValue() != null)
                        cell.setLabel(WebUtils.format(field));
                    if (managedComponents != null)
                        managedComponents.put(field.getName(), cell);
                    item.appendChild(cell);
                }
                if (expressionEngine != null)
                    expressionEngine.eval(item, evalMap, managedComponents);
            }
        }
        if (!Strings.isBlank(list.getDraggable()))
            item.setDraggable(list.getDraggable());
        if (elh != null)
            item.addEventListener(eventName, this);
        list.getItems().add(index, item);
    }

    private Listitem createRow(int index, FieldVo[] fvs, Map<String, Object> evalMap,
                               Map<String, Component> managedComponents, TotalHelper[] groupTotals) {
        if (Strings.isBlank(list.getValueField()))
            list.setValueField(fvs[0].getName());
        Listitem item = new Listitem();
        if (!Strings.isBlank(list.getRowStyle()))
            item.setStyle(list.getRowStyle());
        item.setValue(fvs);
        int size = list.getCols();
        if (size == 0) {
            size = fvs.length;
            if (list.getListhead() != null && !list.getListhead().getChildren().isEmpty())
                size = list.getListhead().getChildren().size();
            list.setCols(size);
            columns = new String[size];
        }
        if (evalMap != null) {
            for (FieldVo fv : fvs)
                evalMap.put(fv.getName(), fv.getValue());
        }
        for (int i = 0; i < fvs.length; i++) {
            FieldVo field = fvs[i];
            if (totalHelper != null) {
                for (int j = 0; j < totalHelper.length; j++) {
                    if (totalHelper[j].name.equals(field.getName()) && field.getValue() != null) {
                        totalHelper[j].caculate(field.getValue().toString());
                        if (groupTotals != null)
                            groupTotals[j].caculate(field.getValue().toString());
                        break;
                    }
                }
            }
            if (i < columns.length) {
                columns[i] = field.getName();
                Listcell cell = new Listcell();
                if (field.getValue() != null)
                    cell.setLabel(WebUtils.format(field));
                if (managedComponents != null)
                    managedComponents.put(field.getName(), cell);
                item.appendChild(cell);

            } else
                break;
        }
        if (!Strings.isBlank(list.getDraggable()))
            item.setDraggable(list.getDraggable());
        if (elh != null)
            item.addEventListener(eventName, this);
        list.getItems().add(index, item);
        return item;
    }

    private void loadGroup(ExpressionEngine expressionEngine, Map<String, Object> evalMap,
                           Map<String, Component> managedComponents, Listgroup group) {
        GetListVo vo = new GetListVo(list.getDbId(), list.getGroupQuery());
        GetListRequestMessage req = new GetListRequestMessage(main.getId(), vo);
        FieldVo[] fvs = group.getValue();
        for (FieldVo f : fvs) {
            if (f.getName().equals(list.getGroupField())) {
                vo.setParameter(f.getValue());
                break;
            }
        }
        IResponseMessage<?> resp = cs.getList(req);
        if (resp.isSuccess()) {
            TotalHelper[] groupTotals = null;
            if (totalHelper != null) {
                groupTotals = new TotalHelper[totalHelper.length];
                for (int i = 0; i < totalHelper.length; i++)
                    groupTotals[i] = new TotalHelper(totalHelper[i].name);
            }
            List<FieldVo[]> tmp = (List<FieldVo[]>) ((GetListResponseMessage) resp).getBody();
            int index = group.getIndex() + 1;
            for (FieldVo[] row : tmp) {
                if (evalMap != null) {
                    evalMap.clear();
                    managedComponents.clear();
                }
                Listitem item = createRow(index++, row, evalMap, managedComponents, groupTotals);
                if (expressionEngine != null)
                    expressionEngine.eval(item, evalMap, managedComponents);
            }
            if (groupTotals != null)
                createFoot(group, groupTotals);

        } else
            Clients.wrongValue(list, (String) resp.getBody());
        group.removeEventListener(Events.ON_OPEN, this);
    }

    @Override
    public void onEvent(Event evt) throws Exception {
        if (evt.getName().equals(ZulEvents.ON_PAGING)) {
            int pageNo = ((PagingEvent) evt).getActivePage() + 1;
            gv.setGetCount(false);
            gv.setPageNo(pageNo);
            GetListRequestMessage req = new GetListRequestMessage(main.getId(), gv);
            IResponseMessage<?> resp = cs.getList(req);
            if (resp.isSuccess()) {
                list.getItems().clear();
                List<FieldVo[]> data = (List<FieldVo[]>) ((GetListResponseMessage) resp).getBody();
                if (!data.isEmpty())
                    redraw(data);
            } else
                throw new WrongValueException(list, (String) resp.getBody());
        } else if (evt.getName().equals(Events.ON_OPEN)) {
            OpenEvent oe = (OpenEvent) evt;
            if (oe.isOpen()) {
                ExpressionEngine expressionEngine = null;
                Map<String, Object> evalMap = null;
                Map<String, Component> managedComponents = null;
                if (!Strings.isBlank(list.getRowScript())) {
                    expressionEngine = SupportFactory.getExpressionEngine(main, list.getRowScript());
                    expressionEngine.compile();
                    evalMap = new HashMap<String, Object>();
                    managedComponents = new HashMap<String, Component>();
                }
                try {
                    loadGroup(expressionEngine, evalMap, managedComponents, (Listgroup) evt.getTarget());
                } finally {
                    if (expressionEngine != null) {
                        evalMap = null;
                        managedComponents = null;
                        expressionEngine.destroy();
                        expressionEngine = null;
                    }
                }
                // 更新总计
                if (totalHelper != null)
                    createFoot(list, totalHelper);
            }
        } else if (evt.getName().equals(eventName))
            elh.onEvent(new Event(eventName, list, evt.getData()));
    }

    @Override
    public void reload(Component widget) {
        createHeads();
        createItems();
    }

    @Override
    public void export(String type, Object... exportHeaders) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            ListVo entity = new ListVo(list.getId());
            List<Component> head = list.getListhead().getChildren();
            int size = head.size();
            List<ListHeaderVo> headers = new ArrayList<ListHeaderVo>(size);
            for (int i = 0; i < size; i++) {
                ListHeaderVo headerVo = new ListHeaderVo();
                headerVo.setName(((Listheader) head.get(i)).getLabel());
                if (exportHeaders.length == 0)
                    headerVo.setExport(true);
                else {
                    if (exportHeaders[0] instanceof Number) {
                        for (Object idx : exportHeaders) {
                            if (((Number) idx).intValue() == i) {
                                headerVo.setExport(true);
                                break;
                            }
                        }
                    } else if (exportHeaders[0] instanceof Strings) {
                        for (Object name : exportHeaders) {
                            if (name.equals(headerVo.getName())) {
                                headerVo.setExport(true);
                                break;
                            }
                        }
                    }
                }
                headers.add(headerVo);
            }
            entity.setHeaders(headers);
            if (type.equalsIgnoreCase("pdf")) {
                PdfExporter exporter = new PdfExporter();
                exporter.export(entity, list, out);
                AMedia amedia = new AMedia(list.getId() + ".pdf", "pdf", "application/pdf", out.toByteArray());
                Filedownload.save(amedia);
            } else {
                ExcelExporter exporter = new ExcelExporter();
                exporter.export(entity, list, out);
                AMedia amedia = new AMedia(list.getId() + ".xlsx", "xlsx", "application/file", out.toByteArray());
                Filedownload.save(amedia);
            }
        } catch (Exception ex) {
            if (log.isErrorEnabled())
                log.error("export {},{}", list.getId(), ex);
            throw new RuntimeException(ex);
        } finally {
            IOUtils.closeQuietly(out);
        }
    }

    public void save(String logic) {
        List<CrossRowVo> data = list.getCrossData(false);
        if (!data.isEmpty()) {
            ComponentService cs = ServiceLocator.lookup(ComponentService.class);
            IResponseMessage<?> resp = cs.saveList(new SaveListRequestMessage(main.getId(), logic, list.getCrossData(false)));
            if (!resp.isSuccess())
                throw new BackendException(resp);
            for (CrossRowVo crv : data)
                crv.setModify(false);
        }
    }

    private final static Logger log = LoggerFactory.getLogger(ListboxBuilder.class);

    private class CrosstableEventListener implements EventListener<Event> {
        @Override
        public void onEvent(Event event) throws Exception {
            Listcell lc = (Listcell) event.getTarget().getParent();
            while (lc.getValue() == null)
                lc = (Listcell) lc.getPreviousSibling();
            CrossRowVo crv = lc.getValue();
            Object value = PageUtils.getValue(event.getTarget(), false);
            String name = (String) event.getTarget().getAttribute("field");
            for (FieldVo fv : crv.getData()) {
                if (fv.getName().equals(name)) {
                    fv.setValue(value);
                    break;
                }
            }
            crv.setModify(true);
        }
    }

    private class Node {
        Object key;
        String name;
        List<FieldVo[]> children = new ArrayList<FieldVo[]>();
    }

    private class TotalHelper {
        String name;
        BigDecimal total = new BigDecimal(0);

        TotalHelper(String name) {
            this.name = name;
        }

        void caculate(String amount) {
            BigDecimal val = new BigDecimal(amount);
            total = total.add(val);
        }
    }

    /**
     * Run Task
     *
     * @param args
     */
    @Override
    public void go(Object[] args) {
        ExtUtils.go(main, list, args);
    }
}
